/**
  ******************************************************************************
  * @file   pd_h_flash.c
  * @author Chris
  * @since  2023/11/24
  *
  * @brief  Peripheral driver of FLASH(Hardware-Implemented).
  *
  ******************************************************************************
  */

#include "stm32g4xx_ll_system.h"
#include "pd_h_flash.h"
#include "delay.h"

/** region Declaration and Definition Region */

/** region Variables and Constants Region */
#define FLASH_NB_DOUBLE_WORDS_IN_ROW  32

LL_FLASH_ProcessTypeDef pFlash = {.Lock = FLASH_UNLOCKED,
        .ErrorCode = FLASH_ERROR_NONE,
        .ProcedureOnGoing = FLASH_PROC_NONE,
        .Address = 0U,
        .Bank = FLASH_BANK_1,
        .Page = 0U,
        .NbPagesToErase = 0U,
        .CacheToReactivate = FLASH_CACHE_DISABLED};
/** endregion Variables and Constants Region */

/** region Function Declaration Region */
void PD_H_FLASH_MassErase(uint32_t Banks);

void PD_H_FLASH_PageErase(uint32_t Page, uint32_t Banks);

void PD_H_FLASH_FlushCaches(void);

void PD_H_FLASH_ProgramDoubleWord(uint32_t Address, uint64_t Data);

void PD_H_FLASH_ProgramFast(uint32_t Address, uint32_t DataAddress);

FlashStatus PD_H_FLASH_WaitForLastOperation(uint32_t Timeout);
/** endregion Function Declaration Region */

/** endregion Declaration and Definition Region */




/** region public function region */

uint32_t PD_H_FLASH_ReadWord(uint32_t address) {
    return *((__IO uint32_t *) address);
}

uint32_t PD_H_FLASH_ReadHalfWord(uint32_t address) {
    return *((__IO uint16_t *) address);
}

uint32_t PD_H_FLASH_ReadByte(uint32_t address) {
    return *((__IO uint8_t *) address);
}


/**
 * @brief   unlock flash
 *
 * @note    none
 * @retval  @see HAL_StatusTypeDef
 */
FlashStatus PD_H_FLASH_Unlock(void) {
    FlashStatus status = FLASH_OK;

    /* verify if Flash is unlocked */
    // Bit 31 LOCK: FLASH_CR Lock
    // This bit is set only. When set, the FLASH_CR register is locked. It is cleared by
    // hardware after detecting the unlock sequence.
    if (READ_BIT(FLASH->CR, FLASH_CR_LOCK) == 0U) {
        return status;
    }

    /* Authorize the FLASH Registers access */
    // The following sequence is used to unlock this register:
    // 1. Write KEY1 = 0x45670123 in the Flash key register (FLASH_KEYR)
    // 2. Write KEY2 = 0xCDEF89AB in the FLASH_KEYR register
    WRITE_REG(FLASH->KEYR, FLASH_KEY1);
    WRITE_REG(FLASH->KEYR, FLASH_KEY2);

    /* verify if Flash is unlocked successfully */
    if (READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U) {
        status = FLASH_ERROR;
    }

    return status;
}

/**
 * @brief  Perform a mass erase or erase the specified FLASH memory pages.
 *
 * @param[in]  pEraseInit pointer to an FLASH_EraseInitTypeDef structure that
 *         contains the configuration information for the erasing.
 * @param[out]  PageError pointer to variable that contains the configuration
 *         information on faulty page in case of error (0xFFFFFFFF means that all
 *         the pages have been correctly erased).
 * @retval HAL_Status
 */
FlashStatus PD_H_FLASH_Erase(LL_FLASH_EraseInitTypeDef *pEraseInit, uint32_t *PageError) {
    FlashStatus status;

    /* Wait for last operation to be completed */
    status = PD_H_FLASH_WaitForLastOperation((uint32_t) FLASH_TIMEOUT_VALUE);
    if (status != FLASH_OK) {
        return status;
    }

    pFlash.ErrorCode = FLASH_ERROR_NONE;

    /* Deactivate the cache if they are activated to avoid data misbehavior */
    // Bit 9 ICEN: Instruction cache enable
    //      0: Instruction cache is disabled
    //      1: Instruction cache is enabled
    // Bit 10 DCEN: Data cache enable
    //      0: Data cache is disabled
    //      1: Data cache is enabled
    if (READ_BIT(FLASH->ACR, FLASH_ACR_ICEN) != 0U) {
        if (READ_BIT(FLASH->ACR, FLASH_ACR_DCEN) != 0U) {
            LL_FLASH_DATA_CACHE_DISABLE();
            pFlash.CacheToReactivate = FLASH_CACHE_ICACHE_DCACHE_ENABLED;
        } else {
            pFlash.CacheToReactivate = FLASH_CACHE_ICACHE_ENABLED;
        }
    } else if (READ_BIT(FLASH->ACR, FLASH_ACR_DCEN) != 0U) {
        LL_FLASH_DATA_CACHE_DISABLE();
        pFlash.CacheToReactivate = FLASH_CACHE_DCACHE_ENABLED;
    } else {
        pFlash.CacheToReactivate = FLASH_CACHE_DISABLED;
    }

    /* Erase FLASH according to the specified type. */
    switch (pEraseInit->TypeErase) {
        case FLASH_TYPEERASE_MASSERASE:
            /* Mass erase to be done */
            PD_H_FLASH_MassErase(pEraseInit->Banks);
            status = PD_H_FLASH_WaitForLastOperation((uint32_t) FLASH_TIMEOUT_VALUE);

            /* If the erase operation is completed, disable the MER1 Bit */
            // Bit 2 MER1: Mass erase
            //      This bit triggers the mass erase (all user pages) when set.
            CLEAR_BIT(FLASH->CR, (FLASH_CR_MER1));

            break;

        case FLASH_TYPEERASE_PAGES:
            /* Page erase to be done */

            /*Initialization of PageError variable*/
            *PageError = 0xFFFFFFFFU;

            for (uint32_t page_index = pEraseInit->Page;
                 page_index < (pEraseInit->Page + pEraseInit->NbPages); page_index++) {
                /* Do page erase. */
                PD_H_FLASH_PageErase(page_index, pEraseInit->Banks);
                status = PD_H_FLASH_WaitForLastOperation((uint32_t) FLASH_TIMEOUT_VALUE);

                /* If the erase operation is completed, disable the PER Bit */
                // Bits 9:3 PNB[6:0]: Page number selection
                //      These bits select the page to erase:
                //          00000000: page 0
                //          00000001: page 1
                //          ...
                //          11111111: page 255
                // Bit 1 PER: Page erase
                //      0: page erase disabled
                //      1: page erase enabled
                CLEAR_BIT(FLASH->CR, (FLASH_CR_PER | FLASH_CR_PNB));

                /* Retry 3 times if erase failure */
                if (status != FLASH_OK) {
                    uint8_t retry = 3;
                    while (retry--) {
                        PD_H_FLASH_PageErase(page_index, pEraseInit->Banks);
                        status = PD_H_FLASH_WaitForLastOperation((uint32_t) FLASH_TIMEOUT_VALUE);
                        if (status == FLASH_OK) {
                            break;
                        }
                    }
                    /* Stop erase procedure and return the faulty page if retry failure. */
                    if (status != FLASH_OK) {
                        *PageError = page_index;
                        break;
                    }
                }
            }
            break;
        default:
            break;
    }

    /* Flush the caches to be sure of the data consistency */
    PD_H_FLASH_FlushCaches();

    return status;
}

/**
  * @brief  Program double word or fast program of a row at a specified address.
  *
  * @param  TypeProgram Indicate the way to program at a specified address.
  *         This parameter can be a value of @ref FLASH_Type_Program.
  * @param  Address specifies the address to be programmed.
  * @param  Data specifies the data to be programmed.
  *         This parameter is the data for the double word program and the address where
  *         are stored the data for the row fast program.
  *
  * @retval HAL_Status
  */
FlashStatus PD_H_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data) {
    FlashStatus status;
    uint32_t prog_bit = 0;

    status = PD_H_FLASH_WaitForLastOperation((uint32_t) FLASH_TIMEOUT_VALUE);
    if (status != FLASH_OK) {
        return status;
    }

    if (TypeProgram == FLASH_TYPEPROGRAM_DOUBLEWORD) {
        /* Program double-word (64-bit) at a specified address */
        PD_H_FLASH_ProgramDoubleWord(Address, Data);
        prog_bit = FLASH_CR_PG;
    } else if ((TypeProgram == FLASH_TYPEPROGRAM_FAST) || (TypeProgram == FLASH_TYPEPROGRAM_FAST_AND_LAST)) {
        /* Fast program a 32 row double-word (64-bit) at a specified address */
        PD_H_FLASH_ProgramFast(Address, (uint32_t) Data);

        /* If it is the last row, the bit will be cleared at the end of the operation */
        if (TypeProgram == FLASH_TYPEPROGRAM_FAST_AND_LAST) {
            prog_bit = FLASH_CR_FSTPG;
        }
    } else {
        /* Nothing to do */
    }

    status = PD_H_FLASH_WaitForLastOperation((uint32_t) FLASH_TIMEOUT_VALUE);

    /* If the program operation is completed, disable the PG or FSTPG Bit */
    if (prog_bit != 0U) {
        CLEAR_BIT(FLASH->CR, prog_bit);
    }

    return status;
}

/**
  * @brief  Lock the FLASH control register access.
  * @retval HAL_Status
  */
FlashStatus PD_H_FLASH_Lock(void) {
    FlashStatus status = FLASH_ERROR;

    /* Set the LOCK Bit to lock the FLASH Registers access */
    // Bit 31 LOCK: FLASH_CR Lock
    //      This bit is set only. When set, the FLASH_CR register is locked. It is cleared by
    //      hardware after detecting the unlock sequence.
    //      In case of an unsuccessful unlock operation, this bit remains set until the next
    //      system reset.
    SET_BIT(FLASH->CR, FLASH_CR_LOCK);

    /* verify Flash is locked */
    if (READ_BIT(FLASH->CR, FLASH_CR_LOCK) != 0U) {
        status = FLASH_OK;
    }

    return status;
}



/** endregion public function region */



/** region private function region */
/**
 * @brief  Erase the specified FLASH memory page.
 *
 * @param  Page FLASH page to erase.
 *         This parameter must be a value between 0 and (max number of pages in the bank - 1).
 * @param  Banks Bank where the page will be erased.
 *         This parameter can be one of the following values:
 *            @arg FLASH_BANK_1: Page in bank 1 to be erased
 *            @arg FLASH_BANK_2: Page in bank 2 to be erased (*)
 * @note   None
 * @retval None
 */
void PD_H_FLASH_PageErase(uint32_t Page, uint32_t Banks) {
    /* Proceed to erase the page */
    // Bits 9:3 PNB[6:0]: Page number selection
    //      These bits select the page to erase:
    //      00000000: page 0
    //      00000001: page 1
    //      ...
    //      11111111: page 255
    MODIFY_REG(FLASH->CR, FLASH_CR_PNB, ((Page & 0xFFU) << FLASH_CR_PNB_Pos));
    // Bit 1 PER: Page erase
    //      0: page erase disabled
    //      1: page erase enabled
    SET_BIT(FLASH->CR, FLASH_CR_PER);
    // Bit 16 START: Start
    //      This bit triggers an erase operation when set. If MER1, MER2 and PER bits are
    //      reset and the STRT bit is set, an unpredictable behavior may occur without
    //      generating any error flag. This condition should be forbidden.
    //      This bit is set only by software, and is cleared when the BSY bit is cleared in
    //      FLASH_SR.
    SET_BIT(FLASH->CR, FLASH_CR_STRT);
}

/**
 * @brief  Mass erase of FLASH memory.
 *
 * @param  Banks Banks to be erased.
 *         This parameter can be one of the following values:
 *            @arg FLASH_BANK_1: Bank1 to be erased
 *            @arg FLASH_BANK_2: Bank2 to be erased (*)
 *            @arg FLASH_BANK_BOTH: Bank1 and Bank2 to be erased (*)
 * @note   None
 * @retval None
 */
void PD_H_FLASH_MassErase(uint32_t Banks) {
    /* Set the Mass Erase Bit for the bank 1 if requested */
    // Bit 2 MER1: Mass erase
    //      This bit triggers the mass erase (all user pages) when set.
    if ((Banks & FLASH_BANK_1) != 0U) {
        SET_BIT(FLASH->CR, FLASH_CR_MER1);
    }

    /* Proceed to erase all sectors */
    // Bit 16 START: Start
    //      This bit triggers an erase operation when set. If MER1, MER2 and PER bits are
    //      reset and the STRT bit is set, an unpredictable behavior may occur without
    //      generating any error flag. This condition should be forbidden.
    //      This bit is set only by software, and is cleared when the BSY bit is cleared in
    //      FLASH_SR.
    SET_BIT(FLASH->CR, FLASH_CR_STRT);
}

/**
 * @brief  Flush the instruction and data caches.
 * @retval None
 */
void PD_H_FLASH_FlushCaches(void) {
    FlashCacheState cache = pFlash.CacheToReactivate;

    /* Flush instruction cache  */
    if ((cache == FLASH_CACHE_ICACHE_ENABLED) ||
        (cache == FLASH_CACHE_ICACHE_DCACHE_ENABLED)) {
        /* Disable instruction cache */
        LL_FLASH_INSTRUCTION_CACHE_DISABLE();
        /* Reset instruction cache */
        LL_FLASH_INSTRUCTION_CACHE_RESET();
        /* Enable instruction cache */
        LL_FLASH_INSTRUCTION_CACHE_ENABLE();
    }

    /* Flush data cache */
    if ((cache == FLASH_CACHE_DCACHE_ENABLED) ||
        (cache == FLASH_CACHE_ICACHE_DCACHE_ENABLED)) {
        /* Reset data cache */
        LL_FLASH_DATA_CACHE_RESET();
        /* Enable data cache */
        LL_FLASH_DATA_CACHE_ENABLE();
    }

    /* Reset internal variable */
    pFlash.CacheToReactivate = FLASH_CACHE_DISABLED;
}

/**
 * @brief  Program double-word (64-bit) at a specified address.
 *
 * @param  Address  specifies the address to be programmed.
 * @param  Data     specifies the data to be programmed.
 * @retval None
 */
void PD_H_FLASH_ProgramDoubleWord(uint32_t Address, uint64_t Data) {
    /* Set PG bit */
    SET_BIT(FLASH->CR, FLASH_CR_PG);

    /* Program first word */
    *(uint32_t *) Address = (uint32_t) Data;

    /* Barrier to ensure programming is performed in 2 steps, in right order
      (independently of compiler optimization behavior) */
    __ISB();

    /* Program second word */
    *(uint32_t *) (Address + 4U) = (uint32_t) (Data >> 32U);
}

/**
 * @brief  Fast program a row double-word (64-bit) at a specified address.
 *
 * @param  Address      specifies the address to be programmed.
 * @param  DataAddress  specifies the address where the data are stored.
 * @retval None
 */
void PD_H_FLASH_ProgramFast(uint32_t Address, uint32_t DataAddress) {
    uint8_t row_index = (2 * FLASH_NB_DOUBLE_WORDS_IN_ROW);
    uint32_t *dest_addr = (uint32_t *) Address;
    uint32_t *src_addr = (uint32_t *) DataAddress;
    uint32_t primask_bit;

    /* Set FSTPG bit */
    // Bit 18 FSTPG: Fast programming
    //      0: Fast programming disabled
    //      1: Fast programming enabled
    SET_BIT(FLASH->CR, FLASH_CR_FSTPG);

    /* Enter critical section: Disable interrupts to avoid any interruption during the loop */
    primask_bit = __get_PRIMASK();
    __disable_irq();

    /* Program the double words of the row */
    do {
        *dest_addr = *src_addr;
        dest_addr++;
        src_addr++;
        row_index--;
    } while (row_index != 0U);

    /* Exit critical section: restore previous priority mask */
    __set_PRIMASK(primask_bit);
}

/**
 * @brief  Wait for a FLASH operation to complete.
 *
 * @note   Wait for the FLASH operation to complete by polling on BUSY flag to be reset.
           Even if the FLASH operation fails, the BUSY flag will be reset and an error
           flag will be set
 * @param  Timeout maximum flash operation timeout(millisecond).
 * @retval HAL_Status
 */
FlashStatus PD_H_FLASH_WaitForLastOperation(uint32_t Timeout) {
    /* Wait for the FLASH operation to complete by polling on BUSY flag to be reset. */
    while (LL_FLASH_GET_FLAG(FLASH_FLAG_BSY)) {
        if (Timeout-- <= 0) {
            return FLASH_TIMEOUT;
        }
        delay_us(1000);
    }

    /* Check FLASH operation error flags */
    uint32_t error = (FLASH->SR & FLASH_FLAG_SR_ERRORS);
    if (error != 0u) {
        pFlash.ErrorCode |= error;
        LL_FLASH_CLEAR_FLAG(error);
        return FLASH_ERROR;
    }

    /* Clear FLASH End of Operation flag if it is set. */
    if (LL_FLASH_GET_FLAG(FLASH_FLAG_EOP)) {
        LL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP);
    }

    return FLASH_OK;
}

/** endregion private function region */
